import abc
from typing import Dict, List

import pandas as pd
from joblib import Parallel, delayed

class BaseStrategy(abc.ABC):
    @abc.abstractmethod
    def calc_alloc_ratio(self, all_df: Dict[str, pd.DataFrame], start_date: str, end_date: str, n_jobs: int = 1) -> pd.Series:
        raise NotImplementedError()


class MultiStrategy(BaseStrategy):
    stg_list: List[BaseStrategy]

    def __init__(self, stg_list: List[BaseStrategy]):
        self.stg_list = stg_list

    def calc_alloc_ratio(self, all_df: Dict[str, pd.DataFrame], start_date: str, end_date: str, n_jobs: int = 1) -> pd.Series:
        if n_jobs == 1:
            res_list = [
                stg.calc_alloc_ratio(all_df=all_df, start_date=start_date, end_date=end_date)
                for stg in self.stg_list
            ]
        else:
            res_list = Parallel(n_jobs=n_jobs)(
                delayed(stg.calc_alloc_ratio)(
                    all_df=all_df, start_date=start_date, end_date=end_date
                )
                for stg in self.stg_list
            )
        res = pd.concat(res_list, axis=1).sum(axis=1) / len(self.stg_list)
        return res

class Z1BaseStrategy(abc.ABC):
    @abc.abstractmethod
    def calc_alloc_ratio(self, all_spot_df: Dict[str, pd.DataFrame], all_swap_df: Dict[str, pd.DataFrame], start_date: str, end_date: str, n_jobs: int = 1) -> pd.Series:
        raise NotImplementedError()


class Z1MultiStrategy(Z1BaseStrategy):
    stg_list: List[Z1BaseStrategy]

    def __init__(self, stg_list: List[Z1BaseStrategy]):
        self.stg_list = stg_list

    def calc_alloc_ratio(self, all_spot_df: Dict[str, pd.DataFrame], all_swap_df: Dict[str, pd.DataFrame], start_date: str, end_date: str, n_jobs: int = 1) -> pd.Series:
        if n_jobs == 1:
            res_list = [
                stg.calc_alloc_ratio(all_spot_df=all_spot_df, all_swap_df=all_swap_df, start_date=start_date, end_date=end_date)
                for stg in self.stg_list
            ]
        else:
            res_list = Parallel(n_jobs=n_jobs)(
                delayed(stg.calc_alloc_ratio)(
                    all_spot_df=all_spot_df, all_swap_df=all_swap_df, start_date=start_date, end_date=end_date
                )
                for stg in self.stg_list
            )
        res = pd.concat(res_list, axis=1).sum(axis=1) / len(self.stg_list)
        return res
